V5 to V6
Prerequisites
Angular V20 Migration
Editor's migration guides
Deprecation of Angular's animations package
Angular has deprecated the animations package to use the native CSS instead.
Your project can lead to some warning issues when linting.
To fix them, follow the official migration documentation
Manual migration instructions
For each Angular project :
- Decline
use-application-buildermigration - Decline
control-flow-migrationmigration - Accept
router-current-navigationmigration
EF Migration History table migration
BIA Framework Migration
Run it automatically by clicking on Migrate button
or
Execute each step manually until step 3 - Apply Diff
Mind to check the output logs to check any errors or missing deleted files
-
SOLUTION 1 : Merging rejected files
-
SOLUTION 2 : Analyzing rejected files - MANUAL MIGRATION ONLY
You must keep your current Rights.cs file even if it is marked as deleted
Use the conflict resolution chapter to help you
For each Angular project :
Download the migration script (.txt - Save link as), then :
Download the rights to permission migration script (.txt - Save link as), then :
For each Angular project :
- validate current path as target path
- accept reformat templates option
If some warning about deprecation of NgIf, NgFor or NgSwitch are printed from the final linting, it means that the execution of ng generate @angular/core:control-flow has ignored the migration for concerned file due to some safety constraints.
You have to manually migrate to control flow instructions the identified files (Angular Documentation)
For manual management of conflitcs case : you can remove the .rej files
Conflict Resolution
all-environments.ts
Keep all your teams definition, there will be used by the migration script for the Team Configuration step
AuditFeature.cs
Into AuditTypeMapper method, integrate your old switch case conditions into the new one :
public override Type AuditTypeMapper(Type type)
{
switch(type.Name)
{
case "MyEntity":
return typeof(MyEntityAudit);
default:
return base.AuditTypeMapper(type);
}
}
public override Type AuditTypeMapper(Type type)
{
return type.Name switch
{
// Your previous mapping here
nameof(MyEntity) => typeof(MyEntityAudit),
// BIAToolKit - Begin AuditTypeMapper
// BIAToolKit - End AuditTypeMapper
nameof(User) => typeof(UserAudit),
_ => base.AuditTypeMapper(type),
};
}
CS projects packages references
Keep your packages references into your .csproj, they will be modified by the migration script on the next steps
Front Manual Steps
Team Configuration
Automatically handled by migration script, for information purpose and manual adjustement only.
From the file all-environments.ts, move the content of your teams configuration into back-end file TeamConfig.cs :
export const allEnvironments = {
teams: [
{
teamTypeId: TeamTypeId.MyTeam,
// Configuration to move below
roleMode: RoleMode.AllRoles,
inHeader: true,
displayOne: false,
displayAlways: false,
teamSelectionCanBeEmpty: false,
label: 'myTeam.headerLabel',
},
]
}
public static class TeamConfig
{
public static readonly ImmutableList<BiaTeamConfig<BaseEntityTeam>> Config = new ImmutableListBuilder<BiaTeamConfig<BaseEntityTeam>>()
{
new BiaTeamConfig<BaseEntityTeam>()
{
TeamTypeId = (int)TeamTypeId.MyTeam,
RightPrefix = "MyTeam",
AdminRoleIds = [(int)RoleId.MyTeamAdmin],
TeamAutomaticSelectionMode = BIA.Net.Core.Common.Enum.TeamSelectionMode.None,
// Configuration from TS
RoleMode = BIA.Net.Core.Common.Enum.RoleMode.AllRoles,
DisplayInHeader = true,
DisplayOne = false,
DisplayAlways = false,
TeamSelectionCanBeEmpty = false
Label = "myTeam.headerLabel",
},
}
}
Then, remove the teams from all-environments.ts
useRefreshAtLanguageChange
In previous version, you'll have to use useRefreshAtLanguageChange property defined into your index components, usually to handle changes of current culture to refresh your data by calling onLoadLazy :
ngOnInit(): void {
super.ngOnInit();
if (this.useRefreshAtLanguageChange) {
this.sub.add(
this.biaTranslationService.currentCulture$
.pipe(skip(1))
.subscribe(() => {
this.onLoadLazy(this.crudItemListComponent.getLazyLoadMetadata());
})
);
}
}
By now, this refresh is automatically handled into the CrudItemsIndexCompoent by using the property useRefreshAtLanguageChange from the crudConfiguration.
So, you can remove from your index component the handler of this.biaTranslationService.currentCulture$ to call onLoadLazy, and set into your feature constants the useRefreshAtLanguageChange to true :
export const announcementCRUDConfiguration: CrudConfig<MyFeature> =
new CrudConfig({
// [...]
useRefreshAtLanguageChange: true,
});
For all cases using the previous useRefreshAtLanguageChange from CrudItemIndexComponent, simply use now the same property from the crudConfiguration.
teamsConfig
The teamsConfig were previously accessed from the login parameters throught the authentication application service getLoginParameters() method :
class MyClass {
constructor(private authAppService: AuthAppService){}
myMethod() {
const teamsConfig = this.authAppService.getLoginParameters().teamsConfig;
}
}
Now you must use the teamsConfig from the application settings service :
class MyClass {
constructor(private appSettingsService: AppSettingsService){}
myMethod() {
const teamsConfig = this.appSettingsService.appSettings.teamsConfig;
}
}
Offline Mode
If you use offline mode, it is now enabled by setting the enableOfflineMode parameter to true in the all-environments.ts file.
Routing for your features
FullPageLayoutComponent is now deprecated, replaced by DynamicLayoutComponent. An automatic migration automatically transforms your routings for the DynamicLayout usage. That script could break the display of your components, because it also replaces the PopupLayoutComponent and use the DynamicLayoutComponent mechanism to display children as popup. In some cases, that can't work:
- If you display popup inside popup
- If your feature has a different navigation process than a standard framework navigation
In any case, we advise to check all your routes access and verify that there is no breaking changes due to the migration script.
If there is, you can rollback the transformation from
PopupLayoutComponenttodata.layoutMode: LayoutMode.popupin that part of the routing.
Back Manual Steps
Package Version
Open the DotNet\Directory.Packages.Project.props file and check that no package version contains a wildcard (*). If a wildcard is found, replace it with the desired version number.
Example Before
<Project>
<ItemGroup>
<!-- Add the versions of your packages here. -->
<!-- <PackageVersion Include="MyPackageNameExample" Version="6.0.4" /> -->
<PackageVersion Include="Microsoft.AspNetCore.Http.Features" Version="2.2.0" />
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.*" />
</ItemGroup>
</Project>
Example After
<Project>
<ItemGroup>
<!-- Add the versions of your packages here. -->
<!-- <PackageVersion Include="MyPackageNameExample" Version="6.0.4" /> -->
<PackageVersion Include="Microsoft.AspNetCore.Http.Features" Version="2.2.0" />
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
</ItemGroup>
</Project>
Audit Entities
For all your previous audit entities inherited from AuditEntity :
- inherits from
AuditKeyedEntity<TEntity, TEntityKey, TAuditKey>for audited entities with single PK - inherits from
AuditEntity<TEntity, TAuditKey>for audited join entities with composite PK
See Audit documentation for dedicated audit tables.
OperationalDomainServiceBase inheritage and override
Inheritage
You must fix your class that inherits from OperationalDomainServiceBase by providing new required generic types :
TDto: DTO mapped to yourTEntityTDtoListItem: DTO mapped to yourTEntityfor your lists display and exportTMapper: mapper between your entity and the DTOTMapperListItem: mapper between your entity and the DTO used for listsTFilterDto: filter type of your entity
Prefer to inherit from CrudAppServiceBase or CrudAppServiceListAndItemBase when you need the OperationDomainServiceBase methods.
Inherit directly from DomainServiceBase if you just need to access to the IRepository.
public class MyService : OperationDomainServiceBase<MyEntity, int>
public class MyService : OperationDomainServiceBase<MyEntityDto, MyEntityDto, MyEntity, int, PagingFilterFormatDto, MyEntityMapper, MyEntityMapper>
You can inherits your class interface from the dedicated interface IOperationDomainServiceBase<TDto, TDtoListItem, TEntity, TKey, TFilterDto> if needed
Overrides
- These methods have been renamed as
{MethodName}GenericAsync:GetRangeAsync<TOtherDto, TOtherMapper, TOtherFilterDto>GetAllAsync<TOtherDto, TOtherMapper>GetCsvAsync<TOtherDto, TOtherMapper, TOtherFilterDto>GetAsync<TOtherDto, TOtherMapper>AddAsync<TOtherDto, TOtherMapper>UpdateAsync<TOtherDto, TOtherMapper>RemoveAsync<TOtherDto, TOtherMapper>SaveSafeAsync<TOtherDto, TOtherMapper>SaveAsync<TOtherDto, TOtherMapper>UpdateFixedAsync<TOtherDto, TOtherMapper>
You should only override the public non generic methods equivalent
public class MyService : OperationDomainServiceBase<MyEntity, int>
{
protected override async Task<(IEnumerable<TOtherDto> Results, int Total)> GetRangeAsync<TOtherDto, TOtherMapper, TOtherFilterDto>(TOtherFilterDto filters = null, int id = default, Specification<MyEntity> specification = null, Expression<Func<MyEntity, bool>> filter = null, string accessMode = "Read", string queryMode = "ReadList", string mapperMode = null, bool isReadOnlyMode = false)
{
// Custom code...
return await base.GetRangeAsync<TOtherDto, TOtherMapper, TOtherFilterDto>(filters, id, specification, filter, accessMode, queryMode, mapperMode, isReadOnlyMode);
}
}
public class MyService : OperationDomainServiceBase<MyEntityDto, MyEntityDto, MyEntity, int, PagingFilterFormatDto, MyEntityMapper, MyEntityMapper>
{
public override async Task<(IEnumerable<MyEntityDto> Results, int Total)> GetRangeAsync(PagingFilterFormatDto filters = null, int id = 0, Specification<MyEntity> specification = null, Expression<Func<MyEntity, bool>> filter = null, string accessMode = "Read", string queryMode = "ReadList", string mapperMode = null, bool isReadOnlyMode = false)
{
// Custom code...
return await base.GetRangeAsync(filters, id, specification, filter, accessMode, queryMode, mapperMode, isReadOnlyMode);
}
}
The transformation for the previous overrides of the protected generic methods into public non generic, and the rename of the generic methods usage, are automatically handled by migration script for classes that inherits from CrudAppServiceBase or CrudAppServiceListAndItemBase.
Please review the migration script actions into your application services.
- Following methods required now one or multiple delegate parameter in order to execute their algorithm with overriden methods :
GetCsvGenericAsync<TOtherDto, TOtherMapper, TOtherFilterDto>SaveSafeGenericAsync<TOtherDto, TOtherMapper>SaveGenericAsync<TOtherDto, TOtherMapper>UpdateFixedGenericAsync<TOtherDto, TOtherMapper>
Example below shows you how to call properly one of them with the correct delegate :
public class MyService : OperationDomainServiceBase<MyEntityDto, MyEntityDto, MyEntity, int, PagingFilterFormatDto, MyEntityMapper, MyEntityMapper>
{
public Task<byte[]> GetCsvCustomAsync()
{
// Use of non generic GetRangeAsync
return this.GetCsvGenericAsync<MyEntityDto, MyEntityMapper, PagingFilterFormatDto>(this.GetRangeAsync);
// Use of generic GetRangeGenericAsync with custom types
return this.GetCsvGenericAsync<MyOtherEntityDto, MyOtherEntityMapper, CustomPagingFilterFormatDto>(this.GetRangeGenericAsync<MyOtherEntityDto, MyOtherEntityMapper, CustomPagingFilterFormatDto>);
// Use of custom generic GetRangeGenericAsync with custom types
return this.GetCsvGenericAsync<MyOtherEntityDto, MyOtherEntityMapper, CustomPagingFilterFormatDto>(this.GetRangeGenericCustomAsync<MyOtherEntityDto, MyOtherEntityMapper, CustomPagingFilterFormatDto>);
}
// Must respect the signature of the delegate GetRangeGenericAsyncDelegate
private async Task<(IEnumerable<TOtherDto> Results, int Total)> GetRangeGenericCustomAsync<TOtherDto, TOtherMapper, TOtherFilterDto>(TOtherFilterDto filters = null, Guid id = default, Specification<Pilot> specification = null, Expression<Func<Pilot, bool>> filter = null, string accessMode = "Read", string queryMode = "ReadList", string mapperMode = null, bool isReadOnlyMode = false)
{
// Custom code...
}
}
:::
PagingFilterFormatDto and AdvancedFilter
If you use AdvancedFilter in your feature, you need to declare the type of AdavancedFilter when declaring your PagingFilterFormatDto.
An error will be thrown if you don't and try to use the advanced filter property because PagingFilterFormatDto doesn't have it anymore.
PagingFilterFormatDto should now be PagingFilterFormatDto<{Feature}AdvancedFilterDto>.
IPagingFilterFormatDto should now be IPagingFilterFormatDto<{Feature}AdvancedFilterDto>.
Example:
protected virtual Specification<Plane> GetPlaneAdvancedFilterSpecification(IPagingFilterFormatDto filters)
{
Specification<Plane> specification = new TrueSpecification<Plane>();
if (filters.AdvancedFilter is not null && filters.AdvancedFilter.EnginesNumberRange is not null)
{}
}
becomes
protected virtual Specification<Plane> GetPlaneAdvancedFilterSpecification(IPagingFilterFormatDto<PlaneAdvancedFilterDto> filters)
{
Specification<Plane> specification = new TrueSpecification<Plane>();
if (filters.AdvancedFilter is not null && filters.AdvancedFilter.EnginesNumberRange is not null)
{}
}
Class declaration should also be modified accordingly:
public class PlaneAppService :
CrudAppServiceBase<PlaneDto, Plane, int, PagingFilterFormatDto, PlaneMapper>,
IPlaneAppService
{}
becomes
public class PlaneAppService :
CrudAppServiceBase<PlaneDto, Plane, int, PagingFilterFormatDto<PlaneAdvancedFilterDto>, PlaneMapper>,
IPlaneAppService
{}
In some functions the parameter type cannot be changed to correctly override the function. When this happens, the type can be checked like this:
protected override void SetGetRangeFilterSpecifications(ref Specification<Plane> specification, IPagingFilterFormatDto filters)
{
specification ??= this.GetFilterSpecification(filters);
if (filters is IPagingFilterFormatDto<PlaneAdvancedFilterDto> planeFilters)
{
specification &= this.GetPlaneAdvancedFilterSpecification(planeFilters);
}
}
Tuple error
You might have errors when you declare the result of WebApiRepository PostAsync, PutAsync, DeleteAsync, etc. in a tuple. Example
(FeatureDto result, bool isSuccessStatusCode, string reasonPhrase) = await this.PutAsync<RemotePlaneDto, RemotePlaneDto>($"{this.url}{dto.Id}", dto);
becomes
(FeatureDto result, bool isSuccessStatusCode, string reasonPhrase, _) = await this.PutAsync<RemotePlaneDto, RemotePlaneDto>($"{this.url}{dto.Id}", dto);
Database Migration
You must create a new database migration in order to apply framework changes to your database scheme :
add-migration MigrationBiaFrameworkV6 -c datacontextupdate-database -context datacontext
Build Pipeline
-
Set the Visual Studio version
- Task: Build solution → Visual Studio Version
- Value: Visual Studio 2026
-
Declare pipeline variables
- Name:
DotNetVersion - Value:
net10.0
- Name:
-
Update paths to target
.NET 10.0- API Tests → Test files
**\$(BuildConfiguration)\$(DotNetVersion)\*$(ProjectName).Test.dll
!**\obj\** - Copy Files Presentation Api → Source Folder
DotNet/$(CompanyName).$(ProjectName).Presentation.Api/bin/$(BuildConfiguration)/$(DotNetVersion) - Copy Files Worker service → Source Folder
DotNet/$(CompanyName).$(ProjectName).WorkerService/bin/$(BuildConfiguration)/$(DotNetVersion) - Copy Files DeployDB → Source Folder
DotNet/$(CompanyName).$(ProjectName).DeployDB/bin/$(BuildConfiguration)/$(DotNetVersion)
- API Tests → Test files